/**
 * This is the MapView. It shows the map of the solar system & galaxy.
 */

package edu.gatech.spacetraderteam3.view;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import edu.gatech.spacetraderteam3.controller.GUIEventHandler;
import edu.gatech.spacetraderteam3.model.GameState;
import edu.gatech.spacetraderteam3.model.Government;
import edu.gatech.spacetraderteam3.model.SpaceGalaxy;
import edu.gatech.spacetraderteam3.model.SpacePlanet;
import edu.gatech.spacetraderteam3.model.SpaceSolarSystem;
import edu.gatech.spacetraderteam3.model.TechLevel;

/**
 * Will eventually show all the systems.
 * 
 * @author Alexander Ikonomidis
 * @version 0.1
 */
public class ViewSystemMap extends ViewSystem {
    /**
     * A map of systems to whether they're visitable.
     */
    private Map<SpaceSolarSystem, Boolean> visitableSystems;

    /**
     * The window that shows the galaxy.
     */
    private final SGGWindow galaxyMap;

    /**
     * The window that shows the solar system.
     */
    private final SGGWindow solarSystemMap;

    /**
     * The button that takes the player back to the planet view.
     */
    private final SGGButton backToPlanetButton;

    /**
     * The list of buttons the player can use to select a planetary destination.
     */
    private final List<SGGButton> planetSelectButtons;

    /**
     * The galaxy.
     */
    private final SpaceGalaxy galaxy;

    /**
     * The current solar system.
     */
    private SpaceSolarSystem currentSystem;

    /**
     * The current planet.
     */
    private SpacePlanet planet;

    /**
     * The list of orbital objects to move and draw.
     */
    private List<Orbital> orbitals;

    /**
     * The list of buttons to show on the galaxy view.
     */
    private final Map<SpaceSolarSystem, SGGButton> galaxyButtons;

    /**
     * The sun of the active solar system.
     */
    private Assets.SunIcon sun;

    /**
     * The comparator used to order planets so they overlap properly.
     */
    private final Comparator<Orbital> planetSorter;

    /**
     * The thread pool used for rendering.
     */
    private ExecutorService pool;

    /**
     * The variable used to wobble the sun.
     */
    private double sunTime = 0;

    /**
     * Whether the galaxy is being displayed curently or not.
     */
    private boolean showGalaxy = true;

    /**
     * Constructor for the System Map View.
     * 
     * @param galaxy
     *            the galaxy to poll for galactic information.
     */
    public ViewSystemMap(SpaceGalaxy galaxy) {
        this.galaxy = galaxy;
        planetSelectButtons = new ArrayList<SGGButton>();
        galaxyMap = new SGGWindow(0, 0, wid, hei, GameState.MAP);
        galaxyMap.showContainer(false);

        solarSystemMap = new SGGWindow(0, 0, 500, 100, GameState.MAP); // $codepro.audit.disable
                                                                       // numericLiterals
        solarSystemMap.showContainer(false);
        solarSystemMap.setAlignment(SGGWindow.Alignment.BOTTOM_LEFT);
        solarSystemMap.add(new SGGButton("GALAXY") {
            {
                setSGGEventListener(new SGGEventListener() {
                    public void doEvent(SGGEvent sgge) {
                        showGalaxy();
                    }
                });
            }
        }, 0, 0);
        planetSorter = new Comparator<Orbital>() {
            public int compare(Orbital o1, Orbital o2) {
                return (int) (o1.y - o2.y); // $codepro.audit.disable
                                            // lossOfPrecisionInCast
            }
        };
        galaxyButtons = new HashMap<SpaceSolarSystem, SGGButton>();
        for (SpaceSolarSystem system : galaxy.getSystems()) {
            SGGButton button = new SGGButton(system.getName());
            button.drawBackground(false);
            button.setColor(Color.GREEN);
            final SpaceSolarSystem finalSystem = system;
            button.setSGGEventListener(new SGGEventListener() {
                public void doEvent(SGGEvent sgge) {
                    displaySystem(finalSystem);

                }
            });
            galaxyMap.add(button, 0, 0);
            galaxyButtons.put(system, button);
        }
        backToPlanetButton = new SGGButton("BACK") {
            {
                setSGGEventListener(new SGGEventListener() {
                    public void doEvent(SGGEvent sgge) {
                        GUIEventHandler.backToPlanet();
                        displaySystem(currentSystem);
                    }
                });
            }
        };
        galaxyMap.add(backToPlanetButton, 0, hei);
        showGalaxy();
    }

    /**
     * Displays the galaxy.
     */
    public final void showGalaxy() {
        showGalaxy = true;
        galaxyMap.setVisible(true);
        solarSystemMap.setVisible(false);
    }

    /**
     * Sets which systems the player should be allowed to travel to.
     * 
     * @param visitableSystems
     *            the list of systems mapped to whether they are reachable.
     */
    public void setVisitableSystems(Map<SpaceSolarSystem, Boolean> visitableSystems) {
        this.visitableSystems = visitableSystems;
        for (SpaceSolarSystem system : visitableSystems.keySet()) {
            galaxyButtons.get(system).setFocusable(visitableSystems.get(system));
        }
    }

    /**
     * Sets the active planet.
     * 
     * @param planet
     *            the current planet
     */
    public void setPlanet(SpacePlanet planet) {
        this.planet = planet;
    }

    /**
     * Displays a particular system in the solar system view.
     * 
     * @param system
     *            the system to show
     */
    private void displaySystem(SpaceSolarSystem system) {
        if (planetInfoDialog != null) {
            planetInfoDialog.dispose();
        }
        for (SGGButton button : planetSelectButtons) {
            solarSystemMap.remove(button);
        }
        planetSelectButtons.clear();
        for (int i = 0; i < system.getPlanets().length; i++) {
            SGGButton button = new SGGButton(system.getPlanets()[i].getName());
            if (system.getPlanets()[i].equals(planet)) {
                button.setFocusable(false);
                button.setColor(Color.GREEN);
            }
            planetSelectButtons.add(button);
            solarSystemMap.add(button, 100 * i, 50); // $codepro.audit.disable
                                                     // numericLiterals
            final SpacePlanet finalPlanet = system.getPlanets()[i];
            System.out.println("display system: " + finalPlanet.getName());
            SGGEventListener buttonListener = new SGGEventListener() { // $codepro.audit.disable
                                                                       // anonymousClassInLoop
                public void doEvent(SGGEvent sgge) {
                    planetInfoDialog = buildPlanetInfoDialog(finalPlanet);
                }
            };
            button.setSGGEventListener(buttonListener);
        }
        galaxyMap.setVisible(false);
        solarSystemMap.setVisible(true);
        orbitals = new ArrayList<Orbital>();
        sun = system.getSun();
        for (SpacePlanet p : system.getPlanets()) {
            Orbital o = new Orbital(p.getTexture().image(), p.getOrbitalRadius(),
                    p.getOrbitalSpeed()) {
                {
                    setCenter(wid >> 1, hei >> 1);
                }
            };
            orbitals.add(o);
            if (p.equals(planet)) {
                o.setActive(true);
            }
        }
        pool = Executors.newFixedThreadPool(orbitals.size());
        showGalaxy = false;
    }

    private SGGLabel planetNameLabel;
    private SGGLabel planetTechLabel;
    private SGGLabel planetGovtLabel;
    private SGGLabel planetResrLabel;
    private SGGWindow planetInfoDialog;

    public SGGWindow buildPlanetInfoDialog(SpacePlanet planet) {
        final SGGWindow window = new SGGWindow(0, 0, 300, 200, GameState.MAP);
        GUIEventHandler.getPlanetInfo(planet);
        SGGButton goButton = new SGGButton("GO!");
        final SpacePlanet finalPlanet = planet;
        goButton.setSGGEventListener(new SGGEventListener() {
            public void doEvent(SGGEvent sgge) {
                GUIEventHandler.travelToPlanet(finalPlanet);
            }
        });
        window.add(planetNameLabel, 30, 30);
        window.add(planetGovtLabel, 30, 50);
        window.add(planetTechLabel, 30, 70);
        window.add(planetResrLabel, 30, 90);
        window.add(goButton, 30, 110);
        return window;
    }

    public void setPlanetInfo(String name, TechLevel tech, Government govt, String resr) {
        planetNameLabel = new SGGLabel(name);
        planetTechLabel = new SGGLabel("Tech Level: " + tech.toString());
        planetGovtLabel = new SGGLabel("Government: " + govt.toString());
        planetResrLabel = new SGGLabel("Needed Res: " + resr);
    }

    /**
     * Sets the current system.
     * 
     * @param system
     *            the new current system
     */
    public void setSystem(SpaceSolarSystem system) {
        this.currentSystem = system;
        displaySystem(system);
    }

    /**
     * Paints either the current solar system or the galaxy, depending on what the player
     * is doing.
     * 
     * @param g
     *            - the graphics object
     */
    public void paint(Graphics2D g) {// TODO: This can be threaded!
        super.paint(g);
        if (showGalaxy) {
            galaxyMap.setDimensions(wid, hei); // don't paint

            final double scale = 0.7;
            final double imgWidH = galaxy.getImage().getWidth() / 2.0 * scale;
            final double imgHeiH = galaxy.getImage().getHeight() / 2.0 * scale;
            g.drawImage(galaxy.getImage(), (int) (wid / 2.0 - imgWidH),
                        (int) (hei / 2.0 - imgHeiH), (int) (imgWidH * 2.0),
                        (int) (imgHeiH * 2.0), null);

            double theta = currentSystem.getOrbitingAngle();
            double radius = currentSystem.getOrbitingRadius();
            final int currentX = (int) ((wid >> 1) + radius * Math.cos(theta));
            final int currentY = (int) ((hei >> 1) + radius * Math.sin(theta));
            for (SpaceSolarSystem system : galaxy.getSystems()) {
                theta = system.getOrbitingAngle();
                radius = system.getOrbitingRadius();
                int posX = (wid >> 1) + (int) (radius * Math.cos(theta));
                int posY = (hei >> 1) + (int) (radius * Math.sin(theta));
                if (visitableSystems.containsKey(system) && visitableSystems.get(system)) {
                    g.setColor(Color.CYAN);
                    g.drawLine(currentX + 5, currentY + 5, posX + 5, posY + 5);
                }
                g.setColor(Color.GREEN);
                g.drawOval(posX, posY, 10, 10);
                g.drawLine(posX, posY, posX - 10, posY - 10);
                g.drawLine(posX - 10, posY - 10, posX - 90, posY - 10);
                SGGButton button = galaxyButtons.get(system);
                button.setOffset(posX - 90, posY - 10 - g.getFontMetrics().getHeight());
                button.paint(g);
            }
            g.setColor(Color.RED);
            g.drawString(">>>", currentX - 110, currentY - 12);
            g.drawRect(currentX - 90, currentY - 24, 80, 11);
            g.drawString("<<<", currentX - 10, currentY - 12);
            backToPlanetButton.paint(g);
        } else {
            Collections.sort(orbitals, planetSorter);
            for (Orbital orbital : orbitals) {
                orbital.drawOrbit(g, false);
                if (orbital.shouldDrawBehind()) {
                    orbital.paint(g);
                }
            }

            final int diameter = 400;
            sun.paint(g, sunTime, diameter, wid >> 1, hei >> 1);

            for (Orbital orbital : orbitals) {
                orbital.drawOrbit(g, true);
                if (!orbital.shouldDrawBehind()) {
                    orbital.paint(g);
                }
            }
            if (planetInfoDialog != null)
                planetInfoDialog.paint(g);
        }
        solarSystemMap.paint(g);
    }

    /**
     * Recalculates things for drawing based ont the new window size.
     */
    public void onWindowResize() {
        super.onWindowResize();
        backToPlanetButton.setOffset(5, hei - 50);
        if (!showGalaxy) {
            for (Orbital orbital : orbitals) {
                orbital.setCenter(wid >> 1, hei >> 1);
            }
        }
    }

    /**
     * Animates the various animated things.
     */
    public void doAction() {
        super.doAction();
        if (!showGalaxy) {
            sunTime += 1;
            for (int i = 0; i < orbitals.size(); i++) {
                pool.execute(orbitals.get(i).getRunnable());
            }
            try {
                pool.awaitTermination(10, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * The Orbital class. These orbit the suns.
     * 
     * @author Alexander Ikonomidis
     * 
     */
    private static class Orbital extends PaintablePlanet {
        /**
         * The radius the Orbital orbits at.
         */
        private final double radius;

        /**
         * The center point of the orbit.
         */
        private double centerX, centerY;

        /**
         * Whether this is the current planet or not.
         */
        private boolean active = false;

        /**
         * Constructs an orbital.
         * 
         * @param image
         *            its image
         * @param radius
         *            its orbital radius
         * @param speed
         *            its movement speed
         */
        protected Orbital(BufferedImage image, double radius, double speed) {
            super(image, speed);
            this.radius = radius;
        }

        /**
         * Sets this as the current planet.
         * 
         * @param active
         *            whether this should be the active planet.
         */
        public void setActive(boolean active) {
            this.active = active;

        }

        /**
         * Moves the Orbital along its orbit.
         */
        public void move() {
            revTheta += speed;
            x = ellipseA * radius * Math.cos(revTheta) + centerX;
            y = ellipseB * radius * Math.sin(revTheta) + centerY;
            scale = scaleConstant * (((y - centerY) / radius) + 1) * radius / 250;
            // calculates percent of radius up or down, then adds a constant to make sure
            // the answer is always greater than zero.
            // scaleConstant = 0.5;
            // double offsetY = scaleConstant * (x - centerX) * offsetModifier;// final
            // double used to dampen offset
        }

        /**
         * Draws the Orbital.
         * 
         * @param g
         *            - the graphics object
         */
        public void paint(Graphics2D g) {
            final double txScaled = textureWidth * scale;
            final int y = (int) (this.y - (txScaled / 2.0) + (x - centerX) * offsetY);
            final int x = (int) (this.x - (txScaled / 2.0));
            if (active) {
                final Point center = new Point((int) (x + txScaled / 2),
                        (int) (y + txScaled / 2));
                final RadialGradientPaint rGP = new RadialGradientPaint(center,
                        (float) (txScaled * 2), new float[] { 0.1f, 0.5f }, new Color[] {
                                Color.WHITE, new Color(0, 0, 0, 0) });
                g.setPaint(rGP);
                g.fillOval((int) (x - txScaled / 2), (int) (y - txScaled / 2),
                           (int) (txScaled * 2), (int) (txScaled * 2));
            }
            g.drawImage(buff, x, y, (int) (txScaled), (int) (txScaled), null);
        }

        /**
         * Draws the Orbit of the orbital.
         * 
         * @param g
         *            the graphics to paint with
         * @param front
         *            whether to draw the front or back part of the orbit.
         */
        public void drawOrbit(Graphics2D g, boolean front) {
            g.setColor(Color.WHITE);
            int px = Integer.MIN_VALUE, py = Integer.MIN_VALUE;
            for (double t = (front ? 0 : Math.PI); t < (front ? Math.PI : 2 * Math.PI); t += .03) {
                int x = (int) (ellipseA * radius * Math.cos(t) + centerX);
                int y = (int) (ellipseB * radius * Math.sin(t) + centerY + (x - centerX)
                        * offsetY);
                if (px != Integer.MIN_VALUE) {
                    g.drawLine(px, py, x, y);
                }
                px = x;
                py = y;
            }
        }

        /**
         * Sets the center of the orbital's orbit.
         * 
         * @param centerX
         *            the x coordinate
         * @param centerY
         *            the y coordinate
         */
        public void setCenter(int centerX, int centerY) {
            this.centerX = centerX;
            this.centerY = centerY;
        }

        /**
         * @return Whether the Orbital should be drawn behind it's center of orbit.
         */
        public boolean shouldDrawBehind() {
            return y < centerY;
        }

        /**
         * The toString.
         * 
         * @return the x, y, and radius of the orbit.
         */
        public String toString() {
            return centerX + " " + centerY + " " + radius;
        }
    }

    /**
     * The toString.
     * 
     * @return a string describing this class.
     */
    public String toString() {
        return "The system map view.";
    }
}
